#include "lib/decoders/oggvorbis/pvmovdatasource.h"

#include "lib/memory/pvmmemory.h"

////////////////////////////////////////////////////////////////////////////////
static ov_callbacks ov_stream_callbacks;

 size_t pvm_ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
{
	return ((PVMOVDataSource*)datasource)->ReadFunc(ptr, size, nmemb);
}

int pvm_ov_seek_func(void *datasource, pvm_int64 offset, int whence)
{
	return ((PVMOVDataSource*)datasource)->SeekFunc(offset, whence);
}

int pvm_ov_close_func(void *datasource)
{
	return ((PVMOVDataSource*)datasource)->CloseFunc();
}

long   pvm_ov_tell_func(void *datasource)
{
	return ((PVMOVDataSource*)datasource)->TellFunc();
}

PVMOVDataSource::PVMOVDataSource()
{
	stream = NULL;
	ovOpened = false;
	refCount = 0;
	criticalInitialized = PVMInitCritical(&critical);
}

PVMOVDataSource::~PVMOVDataSource()
{
	if(stream)
	{
		stream->Close();
		stream = NULL;
	}
	if(ovOpened)
		ov_clear(&ov);
	ovOpened = false;

	refCount = 0;
	if(criticalInitialized)
		PVMDeinitCritical(&critical);
	criticalInitialized = false;
}

IPvmInStream& PVMOVDataSource::Stream()
{
	return *stream;
}

OggVorbis_File& PVMOVDataSource::OVFile()
{
	return ov;
}

PVMOVDataSource* PVMOVDataSource::Create(IPvmInStream* srcStream, bool copyStream)
{
	if(ov_stream_callbacks.close_func==NULL)
		ov_stream_callbacks.close_func = pvm_ov_close_func;
	if(ov_stream_callbacks.read_func==NULL)
		ov_stream_callbacks.read_func = pvm_ov_read_func;
	if(ov_stream_callbacks.seek_func==NULL)
		ov_stream_callbacks.seek_func = pvm_ov_seek_func;
	if(ov_stream_callbacks.tell_func==NULL)
		ov_stream_callbacks.tell_func = pvm_ov_tell_func;


	PVMOVDataSource* src = new PVMOVDataSource();
	if(src->criticalInitialized==false)
	{
		delete( src);
		return NULL;
	}

	if(copyStream)
	{	
		IPvmInStream *newStream = srcStream->OpenCopy();
		if(!newStream)
		{
			delete( src);
			return NULL;
		}
		src->stream = newStream;
	}
	else
	{
		src->stream = srcStream;
	}

	if(ov_open_callbacks(src, &src->ov, 0, 0, ov_stream_callbacks)!=0)
	{
		delete( src);
		return NULL;
	}

	src->ovOpened = true;

	return src;
}

size_t PVMOVDataSource::ReadFunc(void *ptr, size_t size, size_t nmemb)
{
	if(stream==NULL)
		return 0;
	long bytesToRead = (long)(size*nmemb);
	long readNum = stream->Read(ptr, bytesToRead);
	return readNum/size;
}

pvm_bool PVMOVDataSource::SeekFunc(pvm_int64 offset, int whence)
{
	if(stream==NULL)
		return 1;

	TPvmSeek origin = EPVM_SEEK_BEGIN;
	switch(whence)
	{
	case SEEK_CUR:
		origin = EPVM_SEEK_CURRENT;
		break;
	case SEEK_END:
		origin = EPVM_SEEK_END;
		break;
	case SEEK_SET:
		origin = EPVM_SEEK_BEGIN;
		break;
	}

	pvm_bool seekres = stream->Seek(offset, origin);
	if(seekres)
		return 1;

	return 0;
}

pvm_int PVMOVDataSource::CloseFunc()
{
	return 0;
}

long PVMOVDataSource::TellFunc()
{
	if(stream==NULL)
		return -1;

	return (long)(stream->Tell());
}

int PVMOVDataSource::GetRefCount()
{
	int refC;

	PVMEnterCritical(&critical);
	refC = refCount;
	PVMLeaveCritical(&critical);
	return refC;
}

int PVMOVDataSource::Release()
{
	bool needRet = true;
	int newRefC = refCount;
	PVMEnterCritical(&critical);
	if(refCount>0)
	{
		refCount--;
		if(refCount<0)
			refCount = 0;
		newRefC = refCount;
		needRet = false;
	}
	PVMLeaveCritical(&critical);
	if(needRet)
		return newRefC;
	if(refCount<=0)
	{
		refCount = 0;
		delete( this);
	}
	return newRefC;
}

int PVMOVDataSource::AddRef()
{
	int newRefC;
	PVMEnterCritical(&critical);
	refCount++;
	newRefC = refCount;
	PVMLeaveCritical(&critical);
	return newRefC;
}
